#include "Parser.h"
#include <fstream>
#include <sstream>
#include <random>
#include "Object.h"
#include "Material.h"
#include "Texture.h"
#include "Mesh.h"
#include "Light.h"
#include "Camera.h"
#include "AreaLight.h"
#include "NoneAcc.h"
#include "Grid.h"
#include "kdtree.h"

void mray::Parser::Parse(const std::string& xmlPath)
{
	std::ifstream ifs(xmlPath);
	std::ostringstream oss;
	oss << ifs.rdbuf();
	ifs.close();

	rapidxml::xml_document<> doc;
	std::string str = oss.str();
	doc.parse<0>(const_cast<char*>(str.c_str()));

	//Scene node
	rapidxml::xml_node<>* node= doc.first_node();
	for(node = node->first_node(); node; node = node->next_sibling()){
		switch(node->name()[0]){
		case 'G'://Global
			ParseGlobal(node);
			break;
		case 'A'://AreaLight
			m_areaLigList.push_back(ParseAreaLight(node));
			if(m_areaLigList.back()->m_visible)
				m_objList.push_back(m_areaLigList.back());
			break;
		case 'M'://Material or Model
			switch(node->name()[1]){
			case 'o'://Model
				m_objList.push_back(ParseModel(node));
				break;
			case 'a'://Material
				m_mtlList.push_back(ParseMaterial(node));
				break;
			default:
				break;
			}
			break;
		case 'C'://Camera
			m_camList.push_back(ParseCamera(node));
			break;
		case 'L'://Light
			m_ligList.push_back(ParseLight(node));
			break;
		case 'T'://Texture
			m_texList.push_back(ParseTexture(node));
			break;
		case 'D'://Debug
			ParseDebug(node);
			break;
		default:
			break;
		}
	}
}

void mray::Parser::ParseGlobal(rapidxml::xml_node<>* globalNode)
{
	//TRACER_TYPE
	auto attr = globalNode->first_attribute("tracer_type");
	switch(attr->value()[0]){
	case 'n':
		m_pTracer = new NormalTracer(reinterpret_cast<World*>(this));
		break;
	case 'p'://Path
		m_pTracer = new PathTracer(reinterpret_cast<World*>(this));
		break;
	default:
		break;
	}
	//ray depth
	ParseInt(globalNode, "depth", m_depth);
	//ACC_TYPE
	attr = globalNode->first_attribute("acc_type");
	switch(attr->value()[0]){
	case 'g':
		m_pAcc = new Grid;
		break;
	case 'k':
		m_pAcc = new KDTree;
		break;
	case 'n':
		m_pAcc = new NoneAcc;
		break;
	default:
		break;
	}
	//background texture
	int bgIndex;
	ParseInt(globalNode, "background", bgIndex);
	m_pBgTex = m_texList[bgIndex];
	//anti alising sampler
	m_pAASpl = ParseSampler(globalNode->first_node("Sampler"));
	//output
	auto opNode = globalNode->first_node("Output");
	ParseInt(opNode, "width", m_width);
	ParseInt(opNode, "height", m_height);
}

mray::BaseObject* mray::Parser::ParseModel(rapidxml::xml_node<>* modelNode)
{
	//parse model
	int mtlid = UINT_MAX;
	BaseObject* pObj = nullptr;
	ParseInt(modelNode, "mtlid", mtlid);
	bool dSides;
	ParseBool(modelNode, "dSides", dSides);

	switch (modelNode->first_attribute("type")->value()[0]){
	case 's':{
			vec3 pos; float radius;
			ParseVec3(modelNode, "pos", pos);
			ParseFloat(modelNode, "radius", radius);
			pObj = new Sphere(pos, radius, nullptr, dSides);
			if(mtlid != UINT_MAX)
				pObj->m_pMtl = m_mtlList[mtlid];
			break;
		}//sphere
	case 'm':{
			char* path = modelNode->first_attribute("path")->value();
			bool regenNormal;
			ParseBool(modelNode, "regenNormal", regenNormal);
			pObj = new Mesh(path, regenNormal, m_mtlList[mtlid], dSides);
			break;
		}
	case 't':
		break;
	case 'r':{
			vec3 pos, look, up; float halfw, halfh;
			ParseVec3(modelNode, "pos", pos);
			ParseVec3(modelNode, "look", look);
			ParseVec3(modelNode, "up", up);
			ParseFloat(modelNode, "halfw", halfw);
			ParseFloat(modelNode, "halfh", halfh);
			pObj = new Rectangle(pos, look, up, halfw, halfh, nullptr, dSides);
			if(mtlid != UINT_MAX)
				pObj->m_pMtl = m_mtlList[mtlid];
			break;
		}
		break;
	case 'b':
		break;
	default:
		break;
	}
	return pObj;
}

mray::Camera* mray::Parser::ParseCamera(rapidxml::xml_node<>* cameraNode)
{
	//parse camera
	vec3 pos, tar, up; float fov;
	ParseVec3(cameraNode, "pos", pos);
	ParseVec3(cameraNode, "tar", tar);
	ParseVec3(cameraNode, "up", up);
	ParseFloat(cameraNode, "fov", fov);
	return new Camera(pos, tar, up, fov);
}

mray::BaseLight* mray::Parser::ParseLight(rapidxml::xml_node<>* lightNode)
{
	bool castShadow; vec3 color;
	ParseVec3(lightNode, "color", color);
	ParseBool(lightNode, "cast_shadow", castShadow);
	BaseLight* pLight = nullptr;

	switch(lightNode->first_attribute("type")->value()[0]){
	case 'p':{
			vec3 pos;
			ParseVec3(lightNode, "position", pos);
			pLight = new PointLight(color, pos, castShadow);
			break;
		}
	case 's':{
			
			break;
		}
	case 'd':{
			vec3 dir;
			ParseVec3(lightNode, "dir", dir);
			pLight = new DirectionalLight(color, dir, castShadow);
			break;
		}
	default:
		break;
	}
	return pLight;
}

mray::BaseMaterial* mray::Parser::ParseMaterial(rapidxml::xml_node<>* materialNode)
{
	vec3 color; int texid = UINT_MAX;
	if(ParseInt(materialNode, "texid", texid) == false)
		ParseVec3(materialNode, "color", color);

	BaseMaterial* pMtl = nullptr;

	auto attr = materialNode->first_attribute("type");
	switch(attr->value()[0]){
	case 'l':{
			if(texid == UINT_MAX)
				pMtl = new LambertMaterial(color);
			else
				pMtl = new LambertMaterial(m_texList[texid]);
			break;
		}
	case 'p':{
			float exp, kd, ks;
			ParseFloat(materialNode, "exp", exp);
			ParseFloat(materialNode, "kd", kd);
			ParseFloat(materialNode, "ks", ks);
			if(texid == UINT_MAX)
				pMtl = new PhongMaterial(color, exp, kd, ks);
			else
				pMtl = new PhongMaterial(m_texList[texid], exp, kd, ks);
			break;
		}
	case 'b':{
			float exp, kd, ks;
			ParseFloat(materialNode, "exp", exp);
			ParseFloat(materialNode, "kd", kd);
			ParseFloat(materialNode, "ks", ks);
			if(texid == UINT_MAX)
				pMtl = new BlinnMaterial(color, exp, kd, ks);
			else
				pMtl = new BlinnMaterial(m_texList[texid], exp, kd, ks);
			break;
		}
	case 'm':{
			float noise;
			ParseFloat(materialNode, "noise", noise);
			if(texid == UINT_MAX)
				pMtl = new MirrorMaterial(color, noise);
			else
				pMtl = new MirrorMaterial(m_texList[texid], noise);
			break;
		}
	default:
		break;
	}
	return pMtl;
}

mray::BaseTexture* mray::Parser::ParseTexture(rapidxml::xml_node<>* textureNode)
{
	BaseTexture* pTex;
	auto attr = textureNode->first_attribute("type");
	switch (attr->value()[0]){
	case 'c':
		{
			vec3 color;
			ParseVec3(textureNode, "color", color);
			pTex = new ColorTexture(color);
			break;
		}
	case 'b':
		{
			attr = textureNode->first_attribute("path");
			pTex = new BitmapTexture(attr->value());
			break;
		}
	default:
		break;
	}
	return pTex;
}

bool mray::Parser::ParseFloat(rapidxml::xml_node<>* node, char* name, float& f)const
{
	auto attr = node->first_attribute(name);
	if(attr == nullptr) return false;
	char* str = attr->value();
	if(strlen(str) == 0) return false;
	sscanf_s(str, "%f", &f);
	return true;
}

bool mray::Parser::ParseInt(rapidxml::xml_node<>* node, char* name, int& f) const
{
	auto attr = node->first_attribute(name);
	if(attr == nullptr) return false;
	char* str = attr->value();
	if(strlen(str) == 0) return false;
	f = std::atoi(str);
	return true;
}

bool mray::Parser::ParseVec3(rapidxml::xml_node<>* node, char* name, vec3& f)const
{
	auto attr = node->first_attribute(name);
	if(attr == nullptr) return false;
	char* str = attr->value();
	if(strlen(str) == 0) return false;
	sscanf_s(str, "%f %f %f", &f.x, &f.y, &f.z);
	return true;
}

bool mray::Parser::ParseBool(rapidxml::xml_node<>* node, char* name, bool& b) const
{
	auto attr = node->first_attribute(name);
	if(attr == nullptr) return false;
	char* str = attr->value();
	if(strlen(str) == 0) return false;
	switch(str[0]){
	case 't':
		b = true;
		return true;
	case 'f':
		b = false;
		return true;
	default:
		return false;
	}
}

mray::Parser::~Parser()
{
	for(auto& ptr : m_objList) delete ptr;
	for(auto& ptr : m_camList) delete ptr;
	for(auto& ptr : m_ligList) delete ptr;
	for(auto& ptr : m_areaLigList)
		if(ptr->m_visible == false) 
			delete ptr;
	for(auto& ptr : m_mtlList) delete ptr;
	for(auto& ptr : m_texList) delete ptr;

	delete m_pTracer;
	delete m_pAASpl;
	delete m_pAcc;

}

void mray::Parser::ParseDebug(rapidxml::xml_node<>* debugNode)
{
	ParseBool(debugNode, "render_area", m_renderArea);
	ParseInt(debugNode, "startX", m_startX);
	ParseInt(debugNode, "startY", m_startY);
	ParseInt(debugNode, "areaX", m_areaX);
	ParseInt(debugNode, "areaY", m_areaY);
}

void mray::Parser::ParseSetting(rapidxml::xml_node<>* settingNode)
{

}

void mray::Parser::ParseBackground(rapidxml::xml_node<>* backgroundNode)
{

}

mray::BaseSampler* mray::Parser::ParseSampler(rapidxml::xml_node<>* samplerNode)
{
	BaseSampler* pSpl = nullptr;
	//public attr
	int num_samples, num_sets;
	ParseInt(samplerNode, "num_samples", num_samples);
	ParseInt(samplerNode, "num_sets", num_sets);

	auto attr = samplerNode->first_attribute("type");
	switch(attr->value()[0]){
	case 'a':
		pSpl = new AverageSampler;
		break;
	case 'j':
		pSpl = new JitteredSampler;
		break;
	case 'm':
		pSpl = new MultiJitteredSampler;
		break;
	case 'r':
		pSpl = new RandomSampler;
		break;
	case 'd':
		pSpl = ParseSampler(samplerNode->first_node("Sampler"));
		pSpl = new DiskSampler(pSpl);
		break;
	}
	pSpl->GenerateSamples(num_samples, num_sets);
	return pSpl;
}

mray::AreaLight* mray::Parser::ParseAreaLight(rapidxml::xml_node<>* areaLightNode)
{
	vec3 color;
	bool visible, cast_shadow;
	AreaLight* pAreaLig = nullptr;
	ParseVec3(areaLightNode, "color", color);
	ParseBool(areaLightNode, "visible", visible);
	ParseBool(areaLightNode, "cast_shadow", cast_shadow);
	BaseObject* rec = ParseModel(areaLightNode->first_node("Model"));
	BaseSampler* spl = ParseSampler(areaLightNode->first_node("Sampler"));
	pAreaLig = new AreaLight(*(dynamic_cast<Rectangle*>(rec)), spl, color, cast_shadow, visible);
	delete rec;
	return pAreaLig;
}




